#include <framework/framework_i.h>

/* PIC controller code from xv6 */

#define IRQ_SLAVE 2       // IRQ at which slave connects to master

// Initial IRQ mask has interrupt 2 enabled (for slave 8259A).
#define INIT_IRQMASK (0xFFFF & ~(1<<IRQ_SLAVE))

static void
pic_setmask(uint16_t mask)
{
	 outb(PIC1_DATA, mask);
	 outb(PIC2_DATA, mask >> 8);
}

// Initialize the 8259A interrupt controllers.
int
pic_init(void)
{
	 if (!ekf_sysconf.has_pic)
		  return 0;
	 // mask all interrupts
	 outb(PIC1_DATA, 0xFF);
	 outb(PIC2_DATA, 0xFF);

	 // Set up master (8259A-1)

	 // ICW1:  0001g0hi
	 //    g:  0 = edge triggering, 1 = level triggering
	 //    h:  0 = cascaded PICs, 1 = master only
	 //    i:  0 = no ICW4, 1 = ICW4 required
	 outb(PIC1_COMMAND, 0x11);

	 // ICW2:  Vector offset
	 outb(PIC1_DATA, IRQ_OFFSET);

	 // ICW3:  (master PIC) bit mask of IR lines connected to slaves
	 //        (slave PIC) 3-bit # of slave's connection to master
	 outb(PIC1_DATA, 1 << IRQ_SLAVE);

	 // ICW4:  000nbmap
	 //    n:  1 = special fully nested mode
	 //    b:  1 = buffered mode
	 //    m:  0 = slave PIC, 1 = master PIC
	 //      (ignored when b is 0, as the master/slave role
	 //      can be hardwired).
	 //    a:  1 = Automatic EOI mode
	 //    p:  0 = MCS-80/85 mode, 1 = intel x86 mode
	 outb(PIC1_DATA, 0x1);

	 // Set up slave (8259A-2)
	 outb(PIC2_COMMAND, 0x11);             // ICW1
	 outb(PIC2_DATA, IRQ_OFFSET + 8);      // ICW2
	 outb(PIC2_DATA, IRQ_SLAVE);           // ICW3
	 // NB Automatic EOI mode doesn't tend to work on the slave.
	 // Linux source code says it's "to be investigated".
	 outb(PIC2_DATA, 0x1);                 // ICW4

	 // OCW3:  0ef01prs
	 //   ef:  0x = NOP, 10 = clear specific mask, 11 = set specific mask
	 //    p:  0 = no polling, 1 = polling mode
	 //   rs:  0x = NOP, 10 = read IRR, 11 = read ISR
	 outb(PIC1_COMMAND, 0x68);             // clear specific mask
	 outb(PIC1_COMMAND, 0x0a);             // read IRR by default

	 outb(PIC2_COMMAND, 0x68);             // OCW3
	 outb(PIC2_COMMAND, 0x0a);             // OCW3

	 if (ekf_sysconf.use_pic)
	 {
		  pic_setmask(INIT_IRQMASK);
	 }
	 else pic_setmask(0xFFFF);

	 return 0;
}

/* The following code are copied from OSDev.org with modifications {{{ */
void
pic_timer_init(uint32_t freq)
{
	 if (freq == 0)
	 {
		  /* Disable PIT by Magic */
		  outb(0x43, 0x30);
		  outb(0x40, 0);
		  outb(0x40, 0);
	 }
	 else
	 {
		  // The value we send to the PIT is the value to divide it's input clock
		  // (1193180 Hz) by, to get our required frequency. Important to note is
		  // that the divisor must be small enough to fit into 16-bits.
		  uint32_t divisor = 1193180 / freq;

		  // Send the command byte.
		  outb(0x43, 0x34);

		  // Divisor has to be sent byte-wise, so split here into upper/lower bytes.
		  uint8_t l = (uint8_t)(divisor & 0xFF);
		  uint8_t h = (uint8_t)((divisor>>8) & 0xFF);

		  // Send the frequency divisor.
		  outb(0x40, l);
		  outb(0x40, h);
	 }
}
/* }}} */


void
pic_send_eoi(int irq)
{
     if(irq >= 8)
		  outb(PIC2_COMMAND, PIC_EOI);
     outb(PIC1_COMMAND, PIC_EOI);
}

/*
 * Enable the specified IRQ line.
 */
void
pic_enable(int irq)
{
	 if (irq < 8) {
		  /* irq on master PIC. */
		  outb(PIC1_DATA, inb(PIC1_DATA) & ~(1 << irq));
	 } else {
		  /* irq on slave PIC. */
		  outb(PIC2_DATA, inb(PIC2_DATA) & ~(1 << (irq - 8)));
	 }
}
  
/*
 * Disable the specified IRQ line.
 */
void
pic_disable(int irq)
{
	 if (irq < 8) {
		  /* irq on master PIC. */
		  outb(PIC1_DATA, inb(PIC1_DATA) | (1 << irq));
	 } else {
		  /* irq on slave PIC. */
		  outb(PIC2_DATA, inb(PIC2_DATA) | (1 << (irq - 8)));
	 }
}
